SPB Git

Repository Public

page.tsx 4.2 KB 118 lines tsx

1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// Fiche complète d'une propriété à vendre (copie Immo-Ka) + mesure Vrai-Prix.3import type { Metadata } from "next";4import { cache } from "react";5import { notFound } from "next/navigation";6import { getUnit } from "@/lib/db";7import { getListing, listingsNear } from "@/lib/immoka";8import ListingView, { type ListingPageData } from "@/components/avendre/ListingView";9import { moneyFr } from "@/lib/seo";10import { summaryForListing } from "@/lib/cost/ai/service";1112export const dynamic = "force-dynamic";1314const load = cache((uid: string): ListingPageData | null => {15  const det = getListing(uid);16  if (!det) return null;17  const l = det.listing;18  const u = det.eval?.unit_id ? getUnit(det.eval.unit_id) : undefined;19  const nearby =20    l.lat != null && l.lng != null21      ? listingsNear(l.lat, l.lng, 3, { group: det.group, excludeUid: uid, limit: 6 })22      : [];23  return {24    listing: {25      uid: l.uid,26      source: l.source,27      url: l.url,28      title: l.title,29      address: l.address,30      sector: l.sector,31      city: l.city,32      region: l.region,33      propertyType: l.property_type,34      price: l.price,35      bedrooms: l.bedrooms,36      bathrooms: l.bathrooms,37      powderRooms: l.powder_rooms,38      areaSqft: l.area_sqft,39      lotSqft: l.lot_sqft,40      yearBuilt: l.year_built,41      mls: l.mls,42      brokerName: l.broker_name,43      agency: l.agency,44      description: l.description,45      lat: l.lat,46      lng: l.lng,47      firstSeen: l.first_seen,48      lastSeen: l.last_seen,49    },50    group: det.group,51    images: det.images,52    features: det.features,53    details: det.details,54    priceLog: det.priceLog,55    eval: det.eval,56    unit: u57      ? {58          id: u.id_provinc,59          adresse: u.adresse,60          apt: u.apt,61          municipalite: u.municipalite,62          typeProp: u.type_prop,63          anneeConstruction: u.annee_construction,64          aireEtagesM2: u.aire_etages_m2,65          superficieTerrainM2: u.superficie_terrain_m2,66          nbEtages: u.nb_etages,67          nbLogements: u.nb_logements,68          genreConstruction: u.genre_construction,69          lienPhysique: u.lien_physique,70          valeurTerrain: u.valeur_terrain,71          valeurBatiment: u.valeur_batiment,72          valeurRole: u.valeur_role,73          est2026: u.est_2026,74          p10: u.p10,75          p90: u.p90,76          history: ([2021, 2022, 2023, 2024, 2025, 2026] as const).map((y) => ({77            year: y,78            value: u[`est_${y}`] as number | null,79          })),80        }81      : null,82    nearby,83    aiSummary: safeSummary(uid),84  };85});8687/** Résumé de l'analyse IA du bâtiment (chantier Coût) — jamais bloquant pour la fiche. */88function safeSummary(uid: string) {89  try { return summaryForListing(uid); } catch { return null; }90}9192export async function generateMetadata({ params }: { params: Promise<{ uid: string }> }): Promise<Metadata> {93  const { uid } = await params;94  const d = load(decodeURIComponent(uid));95  if (!d) return { title: "Propriété introuvable" };96  const l = d.listing;97  const where = [l.address, l.city].filter(Boolean).join(", ") || l.title || "Propriété à vendre";98  const est = d.eval?.est ? ` · estimation Vrai-Prix ${moneyFr(d.eval.est)}` : "";99  const title = `${where} — ${moneyFr(l.price)}${est}`;100  const description = `${l.propertyType ?? "Propriété"} à vendre à ${l.city ?? "—"} : prix demandé ${moneyFr(101    l.price102  )}${d.eval?.est ? `, estimation Vrai-Prix ${moneyFr(d.eval.est)} (${moneyFr(d.eval.low)} – ${moneyFr(d.eval.high)})` : ""}. Choisissez vos comparables et comparez votre évaluation à la mesure Vrai-Prix.`;103  return {104    title,105    description,106    alternates: { canonical: `/a-vendre/${encodeURIComponent(l.uid)}` },107    openGraph: { title, description, type: "website", images: d.images[0] ? [{ url: d.images[0] }] : undefined },108    robots: { index: false, follow: true }, // annonces éphémères, contenu tiers109  };110}111112export default async function ListingPage({ params }: { params: Promise<{ uid: string }> }) {113  const { uid } = await params;114  const data = load(decodeURIComponent(uid));115  if (!data) notFound();116  return <ListingView data={data} />;117}118
Files main
Menu

Theme